// file: driver/x86/cpu.c
// autor: jiang xinpeng
// time:2021.8.7
// copyright: (C) by jiangxinpeng,All right are reserved.

#include <os/driver.h>
#include <os/debug.h>
#include <os/initcall.h>
#include <driver/cpu.h>
#include <arch/cpu.h>
#include <arch/mp.h>
#include <arch/frequery.h>
#include <arch/eflags.h>
#include <arch/msr.h>
#include <os/clock.h>
#include <lib/type.h>
#include <lib/unistd.h>
#include <lib/stdio.h>
#include <sys/ioctl.h>
#include <sys/res.h>

static void CpuInfoInit(device_extension_t *extension);
static void CpuPrint(device_extension_t *extension);
static void GetCPUVendor(char *vendor);
static int CheckCPUID();
static void GetCPUID(uint32_t fun, uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d);
static void GetCPUStepping(uint8_t *step);
static void GetCPUModel(uint8_t *model);
static void GetCPUBrand(char *brand);
static void GetCPUFamily(uint8_t *family);
static uint64_t GetCPUFrequery(uint8_t cpu);
static uint64_t GetCPUMaxFrequery(uint8_t cpu);
static uint64_t GetCPUCurFrequery(uint8_t cpu);

/**CPU family*/
char cpu_family_06H[] = "Pentium 4";
char cpu_family_0FH[] = "P6";
char cpu_family_AMD[] = "Unknown AMD Processer";
char cpu_family_AMDK5[] = "AMD K5";
char cpu_family_VIA[] = "VIA Processer";
char cpu_family_SiS[] = "SiS Processer";
char cpu_family_VMware[] = "Vmware Virtual Processor";
char cpu_family_vpc[] = "Hyper-v Virtual Processor";
char cpu_family_Unknown[] = "Unknown";

/**CPU model*/
char cpu_model_06_2AH[] = "Intel Core i7 i5 i3 2xxx";
char cpu_model_06_0FH[] = "Intel Dual-core processor";
char cpu_model_unknown[] = "Unknown";

/* Cpu end of loop */
char cpu_unknown_info[] = "Unknown";

static int CheckCPUID()
{
    uint32_t IDmask = EFLAGS_ID;
    uint32_t res;

    __asm__ __volatile__("pushf\n\t"
                         "mov 4(%%esp),%%eax\n\t"
                         "popf\n\t"
                         "andl %1,%0\n\t"
                         : "=a"(res)
                         : "b"(IDmask));
    return res;
}

static uint64_t __GetCPUMaxFrequery()
{
#ifdef ENABLE_SMP
    processor_info_t *proc = MpGetProcessor();
#endif

    uint32_t low, high;
    ReadMSR(IA_MPERF_MSR, &low, &high);

#ifdef ENABLE_SMP
    proc->max_frequery = ((uint64_t)high << 16) | low;
#endif
    return do_div64((((uint64_t)high << 32) | low), HZ);
}

static uint64_t __GetCPUCurFrequery()
{
#ifdef ENABLE_SMP
    processor_info_t *proc = MpGetProcessor();
#endif

    uint32_t low, high;
    ReadMSR(IA_APERF_MSR, &low, &high);

#ifdef ENABLE_SMP
    proc->actual_frequery = ((uint64_t)high << 32) | low;
#endif
    return do_div64((((uint64_t)high << 32) | low), HZ);
}

static uint64_t GetCPUMaxFrequery(uint8_t cpu)
{
#ifdef ENABLE_SMP
    // appoint to targe cpu
    MpExecAppoint(cpu, __GetCPUMaxFrequery);
    return MpGetProcessorById(cpu)->max_frequery;
#else
    return __GetCPUMaxFrequery();
#endif
}

static uint64_t GetCPUCurFrequery(uint8_t cpu)
{
#ifdef ENABLE_SMP
    // appoint to targe cpu
    MpExecAppoint(cpu, __GetCPUCurFrequery);
    return MpGetProcessorById(cpu)->actual_frequery;
#else
    return __GetCPUCurFrequery();
#endif
}

static uint64_t GetCPUFrequery(uint8_t cpu)
{
#ifdef ENABLE_SMP
    return MpGetProcessorById(cpu)->frequery;
#else
    return __frequery;
#endif
}

static void GetCPUVendor(char *vendor)
{
    uint32_t a, b, c, d;
    uint32_t id;

    GetCPUID(CPU_GETVENDORID, &a, &b, &c, &d);

    if (vendor != NULL)
    {
        // cpuid instruction return vendor id in ebx,ecx,edx
        memcpy(vendor, &b, 4);
        memcpy(vendor + 4, &d, 4);
        memcpy(vendor + 8, &c, 4);
    }
    vendor[12] = '\0'; // write string eof
}

static void GetCPUID(uint32_t fun, uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d)
{
    __asm__ __volatile__("cpuid\n\t"
                         : "=a"(*a), "=b"(*b), "=c"(*c), "=d"(*d)
                         : "eax"(fun));
}

static void GetCPUStepping(uint8_t *step)
{
    uint32_t a, b, c, d;

    GetCPUID(CPU_GETSTEPPING, &a, &b, &c, &d);

    // get eax low 4bits as stepping ID
    *step = a & 0x0f;
}

static void GetCPUModel(uint8_t *model)
{
    uint32_t a, b, c, d;

    GetCPUID(CPU_GETMODEL, &a, &b, &c, &d);

    // get eax bits 4-7
    *model = (((a >> 16) & 0xF) << 4) + ((a >> 4) & 0xF);
}

static void GetCPUFamily(uint8_t *family)
{
    uint32_t a, b, c, d;

    GetCPUID(CPU_GETFAMILY, &a, &b, &c, &d);

    // get eax bits 8-11
    *family = (((a >> 20) & 0xFF) << 4) + ((a >> 8) & 0xF);
}

static void GetCPUBrand(char *brand)
{
    int i = 0;
    uint32_t a, b, c, d;
    uint8_t *buff = brand;

    // if is support
    GetCPUID(CPU_SUPPORTBRAND, &a, &b, &c, &d);
    if (a >= 0x80000004)
    {
        for (i = CPU_GETBRAND1; i <= CPU_GETBRAND3; i++)
        {
            // get cpu brand
            GetCPUID(i, &a, &b, &c, &d);

            // write cpu brand to buffer
            memcpy(buff, &a, 4);
            memcpy(buff + 4, &b, 4);
            memcpy(buff + 8, &c, 4);
            memcpy(buff + 12, &d, 4);
            buff += 16;
        }
    }
    brand[49] = '\0';
}

static iostatus_t CpuEnter(driver_object_t *driver)
{
    iostatus_t status = IO_SUCCESS;
    device_object_t *devobj;
    device_extension_t *extension;
    int i;
    char devname[DEVICE_NAME_LEN+1];

    for (i = 0; i < cpunum; i++)
    {
        memset(devname, 0, DEVICE_NAME_LEN);
        sprintf(devname, "%s%d", DEVICE_NAME, i);
        status = IoCreateDevice(driver, sizeof(device_extension_t), devname, DEVICE_TYPE_PROCESS, &devobj);

        if (status != IO_SUCCESS)
        {
            KPrint(PRINT_ERR "%s: create device failed!\n", __func__);
            IoDeleteDevice(devobj);
            status = IO_FAILED;
        }
        devobj->flags = 0;
        extension = devobj->device_extension;
        extension->cpu = i;

        CpuInfoInit(extension);

        // print CPU info
        CpuPrint(extension);
    }
    return status;
}

static void GetCPUType(uint8_t *type)
{
    uint32_t a, b, c, d;

    GetCPUID(CPU_GETTYPE, &a, &b, &c, &d);
    *type = (a >> 12) & 0xF;
}

static void CpuPrint(device_extension_t *extension)
{
    KPrint(PRINT_INFO "CPU info\n");
    KPrint(PRINT_INFO "vendor:%s brand:%s\n", extension->vendor, extension->brand);
    KPrint(PRINT_INFO "family:%s model:%s\n", extension->family_str, extension->model_str);
    KPrint(PRINT_INFO "type: 0x%x stepping:0x%x\n", extension->type, extension->stepping);
    KPrint(PRINT_INFO "max cpuid: 0x%x max cpuid extension:0x%x\n", extension->max_cpuid, extension->max_cpuidext);
    KPrint(PRINT_INFO "cpu frequery: %u MHZ cur frequery %u MHZ max frequery %u MHZ\n", (uint32_t)GetCPUFrequery(0), (uint32_t)GetCPUCurFrequery(0), (uint32_t)GetCPUMaxFrequery(0));
}

static void CpuInfoInit(device_extension_t *extension)
{
    uint32_t eax, ebx, ecx, edx;

    // get max cpuid
    GetCPUID(0x00000000, &eax, &ebx, &ecx, &edx);
    extension->max_cpuid = eax;

    // get CPU vedor
    GetCPUVendor(extension->vendor);

    // get extension info
    GetCPUID(0x80000000, &eax, &ebx, &ecx, &edx);
    extension->max_cpuidext = eax;

    // get CPU brand info
    if (extension->max_cpuidext >= 0x80000004)
    {
        GetCPUBrand(extension->brand);
    }

    if (!strncmp(extension->vendor, "GenuineIntel", 12))
    {
        GetCPUFamily(&extension->family);
        GetCPUModel(&extension->model);
        GetCPUStepping(&extension->stepping);
        GetCPUType(&extension->type);

        // cpu family and brand info
        if (extension->family == 0x06)
        {
            copy_family_string(extension->family_str, cpu_family_06H);

            if (extension->model == 0x2a)
            {
                copy_model_string(extension->model_str, cpu_model_06_2AH);
            }
            else if (extension->model == 0x0f)
            {
                copy_model_string(extension->model_str, cpu_model_06_0FH);
            }
            else
            {
                copy_model_string(extension->model_str, cpu_model_unknown);
            }
        }
        else if (extension->family == 0x0f)
        {
            copy_family_string(extension->family_str, cpu_family_0FH);
            copy_model_string(extension->model_str, cpu_model_unknown);
        }
        else
        {
            copy_family_string(extension->family_str, cpu_family_Unknown);
            copy_model_string(extension->model_str, cpu_model_unknown);
        }
    }
    else if (!strncmp(extension->vendor, "AuthenticAMD", 12))
    {

        copy_family_string(extension->family_str, cpu_family_AMD);
        copy_model_string(extension->model_str, cpu_model_unknown);
    }
    else if (!strncmp(extension->vendor, "AMDisbetter!", 12))
    {

        copy_family_string(extension->family_str, cpu_family_AMDK5);
        copy_model_string(extension->model_str, cpu_model_unknown);
    }
    else if (!strncmp(extension->vendor, "SiS SiS SiS ", 12))
    {

        copy_family_string(extension->family_str, cpu_family_SiS);
        copy_model_string(extension->model_str, cpu_model_unknown);
    }
    else if (!strncmp(extension->vendor, "VIA VIA VIA ", 12))
    {

        copy_family_string(extension->family_str, cpu_family_VIA);
        copy_model_string(extension->model_str, cpu_model_unknown);
    }
    else if (!strncmp(extension->vendor, "Microsoft Hv", 12))
    {

        copy_family_string(extension->family_str, cpu_family_vpc);
        copy_model_string(extension->model_str, cpu_model_unknown);
    }
    else if (!strncmp(extension->vendor, "VMwareVMware", 12))
    {

        copy_family_string(extension->family_str, cpu_family_VMware);
        copy_model_string(extension->model_str, cpu_model_unknown);
    }
    else
    {
        copy_family_string(extension->family_str, cpu_family_Unknown);
        copy_model_string(extension->model_str, cpu_model_unknown);
    }
}

static iostatus_t CpuDevCtl(device_object_t *device, io_request_t *ioreq)
{
    device_extension_t *extension = device->device_extension;
    uint32_t cmd = ioreq->parame.devctl.code;
    uint32_t arg = ioreq->parame.devctl.arg;
    iostatus_t status = IO_SUCCESS;

    switch (cmd)
    {
   case CPUIO_GETVENDOR:
        if (MemCopyToUser(arg, extension->vendor,strlen(extension->vendor)) < 0)
            status = IO_FAILED;
        break;
    case CPUIO_GETBRAND:
        if (MemCopyToUser(arg, extension->brand,strlen(extension->brand)) < 0)
            status = IO_FAILED;
        break;
    case CPUIO_GETSTEPPING:
        if (MemCopyToUser(arg, extension->stepping,strlen(extension->stepping)) < 0)
            status = IO_FAILED;
        break;
    case CPUIO_GETFAMILY:
        if (MemCopyToUser(arg, extension->family_str,strlen(extension->family_str)) < 0)
            status = IO_FAILED;
        break;
    case CPUIO_GETFREQUERY:
        *(uint32_t *)arg = GetCPUFrequery(extension->cpu);
        break;
    case CPUIO_GETCURFREQUERY:
        *(uint32_t *)arg = GetCPUCurFrequery(extension->cpu);
        break;
    case CPUIO_GETMAXFREQUERY:
        *(uint32_t *)arg = GetCPUMaxFrequery(extension->cpu);
        break;
    default:
        break;
    }
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t CpuExit(driver_object_t *driver)
{
    device_object_t *device, *next;
    list_traversal_all_owner_to_next_safe(device, next, &driver->device_list, list)
    {
        list_del(&device->list);
    }
    // delete driver name
    string_del(&driver->name);
    return IO_FAILED;
}

static iostatus_t CpuOpen(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t CpuClose(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    ioreq->io_status.status = status;
    ioreq->io_status.info = 0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t CpuRead(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    device_extension_t *extension = device->device_extension;
    char *buff = ioreq->user_buff;
    uint32_t len = ioreq->parame.read.len;

    if (len < 0)
    {
        status = IO_FAILED;
    }
    else
    {
        sprintf(buff, "%s %s %s", extension->vendor, extension->brand, extension->family_str);
        status = IO_SUCCESS;
    }
    ioreq->io_status.status = status;
    ioreq->io_status.info = len;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t CpuDriverFunc(driver_object_t *driver)
{
    iostatus_t status = IO_SUCCESS;

    driver->driver_enter = CpuEnter;
    driver->driver_exit = CpuExit;

    driver->dispatch_fun[IOREQ_OPEN] = CpuOpen;
    driver->dispatch_fun[IOREQ_CLOSE] = CpuClose;
    driver->dispatch_fun[IOREQ_READ] = CpuRead;
    driver->dispatch_fun[IOREQ_DEVCTL] = CpuDevCtl;

    string_new(&driver->name, DRIVER_NAME, DRIVER_NAME_LEN);

    return status;
}

static void __init CpuDriverEntry()
{
    KPrint("[driver] create cpu driver\n");
    if (DriverObjectCreate(CpuDriverFunc) < 0)
    {
        KPrint(PRINT_ERR "[driver]:%s create driver failed!\n", __func__);
    }
}

driver_initcall(CpuDriverEntry);